#******************************************************************************
#******************************************************************************

# import libraries

from math import pi, floor

from src import pysoleng as pse #

from numpy.testing import assert_allclose

import datetime as dt

from zoneinfo import ZoneInfo

# test using "!python -m pytest -s --cov --cov-report term-missing"
        
# *****************************************************************************
# *****************************************************************************

class TestTimePlace:
    
    def test_flat_surface(self):
        
        # 1) single azimuth, single slope
        
        fs1 = pse.timeplace.FlatSurface(
            azimuth=45*pi/180, 
            slope=45*pi/180
            )
        
        assert fs1.has_fixed_azimuth()
        assert fs1.has_fixed_slope()
        assert fs1.number_orientations() == 1
        
        # 2) single azimuth, multiple slopes
        
        fs2 = pse.timeplace.FlatSurface(
            azimuth=45*pi/180, 
            slope=[45*pi/180, 0]
            )
        
        assert fs2.has_fixed_azimuth()
        assert not fs2.has_fixed_slope()
        assert fs2.number_orientations() == 2
        
        # 3) single azimuth, multiple slopes
        
        fs3 = pse.timeplace.FlatSurface(
            azimuth=[45*pi/180, 90*pi/180], 
            slope=45*pi/180
            )
        
        assert not fs3.has_fixed_azimuth()
        assert fs3.has_fixed_slope()
        assert fs3.number_orientations() == 2
        
        # 4) multiple azimuths, multiple slopes
        
        fs4 = pse.timeplace.FlatSurface(
            azimuth=[45*pi/180], 
            slope=[45*pi/180]
            )
        
        assert not fs4.has_fixed_azimuth()
        assert not fs4.has_fixed_slope()
        assert fs4.number_orientations() == 1
        
        # *********************************************************************
        # *********************************************************************
        
        # trigger errors
        
        # incorrect input area
        error_raised = False
        try:
            pse.timeplace.FlatSurface(
                azimuth=45*pi/180, 
                slope=45*pi/180,
                area='a'
                )
        except TypeError:
            error_raised = True
        assert error_raised
        
        # incorrect sizes
        error_raised = False
        try:
            pse.timeplace.FlatSurface(
                azimuth=[45*pi/180], 
                slope=[45*pi/180, 60*pi/180]
                )
        except ValueError:
            error_raised = True
        assert error_raised
        
        # incorrect elements in the iterables
        error_raised = False
        try:
            pse.timeplace.FlatSurface(
                azimuth=[45*pi/180, 'a'], 
                slope=[45*pi/180, 60*pi/180]
                )
        except ValueError:
            error_raised = True
        assert error_raised
        
        # incorrect elements in the iterables
        error_raised = False
        try:
            pse.timeplace.FlatSurface(
                azimuth=[45*pi/180, 60*pi/180], 
                slope=[45*pi/180, 'b']
                )
        except ValueError:
            error_raised = True
        assert error_raised
        
        # *********************************************************************
        # *********************************************************************
        
    # *************************************************************************
    # *************************************************************************

    def test_location_creation(self):
        
        #transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
        
        list_locations = [
            # unprojected
            ((30,110),'epsg:4326',(30,110)),
            ((30,-110),'epsg:4326',(30,-110)),
            ((-30,110),'epsg:4326',(-30,110)),
            ((-30,-110),'epsg:4326',(-30,-110)),
            # projected
            ((3339584.723798207, 3503549.8435043753),'epsg:3857',(30,30)),
            ((3339584.723798207, -3503549.8435043753),'epsg:3857',(30,-30)),
            ((-3339584.723798207, 3503549.8435043753),'epsg:3857',(-30,30)),
            ((-3339584.723798207, -3503549.8435043753),'epsg:3857',(-30,-30)), 
            # projected, time
            ((-466939.6922422622, 3361248.752300655),'epsg:32633',(5,30)),
            ((-56351.259575267904, 6693618.35050865),'epsg:32633',(5,60)),
            ((17453.33453025535, 3329329.800484811),'epsg:32633',(10,30)),
            ((221288.77024763188, 6661953.040544909),'epsg:32633',(10,60))
            ]
        
        for location in list_locations:
            
            mylocation = pse.timeplace.Location(
                coordinates=location[0],
                crs_string=location[1])
            
            assert_allclose(mylocation.latitude, location[2][0])
            assert_allclose(mylocation.longitude, location[2][1])
            
        # locations with issues
        
        list_locations = [
            # epsg:4326
            ((30,182),'epsg:4326',(30,30)),     # longitude out of bounds
            ((30,-181),'epsg:4326',(30,30)),    # longitude out of bounds
            ((95,30),'epsg:4326',(30,30)),      # latitute out of bounds
            ((-95,30),'epsg:4326',(30,30)),     # latitute out of bounds
            ((-95,30,0),'epsg:4326',(30,30))    # 3 coordinates
            ]
        
        error_counter = 0
        
        for location in list_locations:
            
            # trigger the errors
            
            try:
                
                mylocation = pse.timeplace.Location(
                    coordinates=location[0],
                    crs_string=location[1])
                
            except ValueError:
                
                error_counter += 1
                
        assert error_counter == len(list_locations)
    
    # *************************************************************************
    # *************************************************************************

    def test_day_of_the_year(self):
        
        list_tests = [
            # non-leap year
            ('2011-01-15 12:00:00-00:00',15),
            ('2011-02-15 12:00:00-00:00',31+15),
            ('2011-03-15 12:00:00-00:00',59+15),
            ('2011-04-15 12:00:00-00:00',90+15),
            ('2011-05-15 12:00:00-00:00',120+15),
            ('2011-06-15 12:00:00-00:00',151+15),
            ('2011-07-15 12:00:00-00:00',181+15),
            ('2011-08-15 12:00:00-00:00',212+15),
            ('2011-09-15 12:00:00-00:00',243+15),
            ('2011-10-15 12:00:00-00:00',273+15),
            ('2011-11-15 12:00:00-00:00',304+15),
            ('2011-12-15 12:00:00-00:00',334+15),
            # leap year: a day is added after february
            ('2012-01-15 12:00:00-00:00',15),
            ('2012-02-15 12:00:00-00:00',31+15),
            ('2012-03-15 12:00:00-00:00',59+1+15), # after february: add +1
            ('2012-04-15 12:00:00-00:00',90+1+15),
            ('2012-05-15 12:00:00-00:00',120+1+15),
            ('2012-06-15 12:00:00-00:00',151+1+15),
            ('2012-07-15 12:00:00-00:00',181+1+15),
            ('2012-08-15 12:00:00-00:00',212+1+15),
            ('2012-09-15 12:00:00-00:00',243+1+15),
            ('2012-10-15 12:00:00-00:00',273+1+15),
            ('2012-11-15 12:00:00-00:00',304+1+15),
            ('2012-12-15 12:00:00-00:00',334+1+15),
            # UTC offsets: + is ahead of UTC (east), - is before UTC (west)
            ('2011-01-15 03:00:00+05:00',15-1),     # 5 hours ahead: 22 pm UTC
            ('2011-02-15 03:00:00+05:00',31+15-1),  # 5 hours ahead: 22 pm UTC
            ('2011-03-15 03:00:00+05:00',59+15-1),  # 5 hours ahead: 22 pm UTC
            ('2011-01-15 22:00:00-05:00',15+1),     # 5 hours before UTC: 3 am UTC
            ('2011-02-15 22:00:00-05:00',31+15+1),  # 5 hours before UTC: 3 am UTC
            ('2011-03-15 22:00:00-05:00',59+15+1),  # 5 hours before UTC: 3 am UTC
            ]
        
        for test in list_tests:
            
            # create datetime object
            
            my_dt = dt.datetime.fromisoformat(test[0])
            
            # get the day of the year
            
            doty = pse.timeplace.day_of_the_year(my_time=my_dt,
                                                 as_integer=True)
            
            # validate it
            
            assert_allclose(doty, test[1])
            
            # get the non-integer variant
            
            doty_ni = pse.timeplace.day_of_the_year(my_time=my_dt,
                                                    as_integer=False)
            
            # validate it
            
            assert_allclose(floor(doty_ni), test[1])
            
    # *************************************************************************
    # *************************************************************************

    def test_is_datetime_aware(self):
        
        list_tests = [
            ('2011-01-15 12:00:00-00:00', True),
            ('2011-01-15 12:00:00', False),
            ]
        
        # for each test
        
        for test in list_tests:
            
            # create datetime object
            
            my_dt = dt.datetime.fromisoformat(test[0])
            
            assert pse.timeplace.is_datetime_aware(my_dt) == test[1]
    
    # *************************************************************************
    # *************************************************************************
    
    def test_finding_standard_meridian(self):
        
        # TODO: get the daylight time savings time
        
        list_tests = [
            # format: coordinates, crs, time, dst offset degrees, true standard meridian
            # Nicosia: 35°10′21″N 33°21′54″E (Kalogirou, 2009; ex. 2.1, page 52)
            ((35.0, 33.3), 'epsg:4326', '2022-11-15 10:43:00+02:00', 0, 30),# winter
            ((35.0, 33.3), 'epsg:4326', '2022-11-15 10:43:00+03:00', 3600, 30),# summer
            # Madison: 43°04′29″N 89°23′03″W (Duffie and Beckman, 2013; ex. 1.5.1, page 11)
            ((43.0, -89.4), 'epsg:4326', '2010-02-03 10:19:00-06:00', 0, -90),# winter
            ((43.0, -89.4), 'epsg:4326', '2010-02-03 10:19:00-05:00', 3600, -90),# summer
            ]
        
        for test in list_tests:
            
            mylocation = pse.timeplace.Location(
                coordinates=test[0],
                crs_string=test[1])
            
            mytime = dt.datetime.fromisoformat(test[2])
            
            # compute the local meridian (subtract dst offset since it is included)
            
            local_meridian = mylocation.local_standard_meridian_longitude(
                mytime, test[3])
            
            assert_allclose(local_meridian, test[4])
            
        # try causing errors
        
        list_tests = [
            # naive datetime objects
            # Nicosia: 35°10′21″N 33°21′54″E (Kalogirou, 2009; ex. 2.1, page 52)
            ((35.0, 33.3), 'epsg:4326', '2022-11-15 10:43:00', 0, 30),# winter
            ((35.0, 33.3), 'epsg:4326', '2022-11-15 10:43:00', 3600, 30),# summer
            # Madison: 43°04′29″N 89°23′03″W (Duffie and Beckman, 2013; ex. 1.5.1, page 11)
            ((43.0, -89.4), 'epsg:4326', '2010-02-03 10:19:00', 0, -90),# winter
            ((43.0, -89.4), 'epsg:4326', '2010-02-03 10:19:00', 3600, -90),# summer
            ]
        
        # error counter
            
        error_counter = 0
        
        for test in list_tests:
            
            # trigger the errors
            
            try:
                mylocation = pse.timeplace.Location(
                    coordinates=test[0],
                    crs_string=test[1])
                
                mytime = dt.datetime.fromisoformat(test[2])
                
                # compute the local meridian (subtract dst offset since it is included)
                
                local_meridian = mylocation.local_standard_meridian_longitude(
                    mytime, test[3])
                
            except ValueError:
                
                error_counter += 1
                
        assert error_counter == len(list_tests)
        
    # *************************************************************************
    # *************************************************************************
        
    def test_local_timezone_with_dst(self):
        
        # create utc time zone
        
        tz_utc = dt.timezone.utc
        
        # create time zone with dst, northern hemisphere
        
        tz_with_dst_nh = ZoneInfo("Portugal")
        
        # create time zone without dst, northern hemisphere
        
        tz_without_dst_nh = ZoneInfo("Iceland")
        
        # create time zone with dst, southern hemisphere
        
        tz_with_dst_sh = ZoneInfo("America/Asuncion")
        
        # create time zone without dst, souther hemisphere
        
        tz_without_dst_sh = ZoneInfo("America/Argentina/Buenos_Aires")
        
        # create tests with datetime objects with and without dst
        
        list_tests = [ # local time object, utc time, is dst active?, utc offset
            # Portugal, (Northern) Summer  
            (dt.datetime(
                year=2012,
                month=6,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_with_dst_nh),
             dt.datetime(
                year=2012,
                month=6,
                day=5, 
                hour=11,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_utc),
             True,
             3600),
            # Portugal, (Northern) Winter
            (dt.datetime(
                year=2012,
                month=12,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_with_dst_nh),
             dt.datetime(
                year=2012,
                month=12,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_utc),
             False,
             0),
            # Iceland, (Northern) Summer  
            (dt.datetime(
                year=2012,
                month=6,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_without_dst_nh),
             dt.datetime(
                year=2012,
                month=6,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_utc),
             False,
             0),
            # Iceland, (Northern) Winter
            (dt.datetime(
                year=2012,
                month=12,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_without_dst_nh),
             dt.datetime(
                year=2012,
                month=12,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_utc),
             False,
             0),
            # Paraguay, (Northern) Summer  
            (dt.datetime(
                year=2012,
                month=6,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_with_dst_sh),
             dt.datetime(
                year=2012,
                month=6,
                day=5, 
                hour=16,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_utc),
             False,
             0),
            # Paraguay, (Northern) Winter
            (dt.datetime(
                year=2012,
                month=12,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_with_dst_sh),
             dt.datetime(
                year=2012,
                month=12,
                day=5, 
                hour=15,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_utc),
             True,
             3600),
            # Argentina, (Northern) Summer  
            (dt.datetime(
                year=2012,
                month=6,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_without_dst_sh),
             dt.datetime(
                year=2012,
                month=6,
                day=5, 
                hour=15,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_utc),
             False,
             0),
            # Argentina, (Northern) Winter
            (dt.datetime(
                year=2012,
                month=12,
                day=5, 
                hour=12,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_without_dst_sh),
             dt.datetime(
                year=2012,
                month=12,
                day=5, 
                hour=15,
                minute=0,
                second=0,
                microsecond=0,
                tzinfo=tz_utc),
             False,
             0)
            ]
        
        # verify that that the utc offset changes
        
        for test in list_tests:
            
            localtime = test[0]
            
            utc_time = test[1]
            
            # verify if there is or not dst-related offset
            
            assert test[2] == (localtime.dst() > dt.timedelta(seconds=0))
            
            assert localtime.dst() == dt.timedelta(seconds=test[3])
            
            #no offset from utc
            
            assert utc_time.utcoffset() == dt.timedelta(seconds=0)
            
            # check if utc times match (note: datetime objects are hashable)
            
            assert localtime.astimezone(tz_utc) == utc_time
        
#******************************************************************************
#******************************************************************************